﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace AtDevice
{
    /// <summary>
    /// Represents a device that communicates using
    /// AT commands defined in 3GPP TS 27.007.
    /// </summary>
    public class AtDevice : IDisposable
    {
        const int READ_BUFFER_SIZE = 16;
        private FileStream _stream;
        private string _path;
        private ISMSInterface _smsInterface;
        private IGSMInterface _gsmInterface;
        private IPhoneBookManager _phonebookInterface;
        internal Dictionary<string, ISpecialHandler> SpecialHandlers = new Dictionary<string, ISpecialHandler>();

        public static AtDevice OpenDevice(string path)
        {
            AtDevice at = new AtDevice();
            at.Open(path);
            at.Initialize();
            return at;
        }

        private AtDevice()
        {
        }

        public string GetPath()
        {
            return _path;
        }

        public ISMSInterface GetSMSInteface()
        {
            return _smsInterface;
        }

        public IGSMInterface GetGSMInterface()
        {
            return _gsmInterface;
        }

        public IPhoneBookManager GetPhoneBookInterface()
        {
            return _phonebookInterface;
        }

        public T GetInterface<T>()
        {
            ISpecialHandler h = SpecialHandlers.FirstOrDefault(x => x.Value is T).Value;
            return (T)h;
        }

        private void Open(string path)
        {
            _path = path;
            _stream = File.Open(_path, FileMode.Open, FileAccess.ReadWrite);
        }

        private void Initialize()
        {
            _smsInterface = new SMSInterface(this);
            _gsmInterface = new GSMInterface(this);
            _phonebookInterface = new PhoneBookManager(this);
        }

        public void Write(string line)
        {
            lock (_stream)
            {
                _stream.Write(new ASCIIEncoding().GetBytes(line), 0, line.Length);
                AtLogger.LogSent(new ASCIIEncoding().GetBytes(line));
            }
        }

        /// <summary>
        /// Executes the AT command. Will throw an exception if fails.
        /// </summary>
        /// <param name="command">The command to execute</param>
        /// <returns>The response</returns>
        public AtResponse ExecuteCommand(AtCommand command)
        {
            this.Write(command.ToString());
            AtResponseParser parser = new AtResponseParser();

            while (!parser.HasResult())
            {
                parser.ParseLine(ReadLine());
            }

            return parser.GetResponse();
        }
        
        private bool IsSingleLine(StringBuilder sb)
        {
            const string LINE_SEPARATOR = "\x0a\x0a";
            return sb.Length > 4 &&
                   sb.ToString().EndsWith(LINE_SEPARATOR);
        }
        
        public string ReadLine()
        {
            lock (_stream)
            {
                string line = ReadTheLine();

                while (SpecialHandlers.Any(handler => handler.Value.IsSpecialLine(line)))
                {
                    ISpecialHandler handler = SpecialHandlers.FirstOrDefault(h => h.Value.IsSpecialLine(line)).Value;
                    handler.HandleLine(line);
                    line = ReadTheLine();
                }
                
                return line;
            }
        }

        private string ReadTheLine()
        {
            byte [] array = new byte[READ_BUFFER_SIZE];
            StringBuilder sb = new StringBuilder(255);
            int bytesRead;

            do {
                bytesRead = _stream.Read(array, 0, READ_BUFFER_SIZE);
                sb.Append(new ASCIIEncoding().GetString(array, 0, bytesRead));
            } while (!IsSingleLine(sb) && bytesRead > 0);
            AtLogger.LogReceived(sb.ToString());

            return sb.ToString().Trim();
        }

        public void Close()
        {
            lock (_stream)
            {
                _stream.Close();
            }
        }

        public void Dispose()
        {
            this.Close();
        }
    }
}
